﻿'----------------------------------------------------------------------
' Created with SharpDevelop
' Author: Adam Yarnott, Blue Ninja Software
' Copyright (c) Adam Yarnott, Blue Ninja Software.
' Date: 8/22/2007
' Time: 9:08 AM
'----------------------------------------------------------------------

Imports System.Runtime.InteropServices

Imports System.IO

Imports BlueNinjaSoftware.HIDLib.Interop
Imports BlueNinjaSoftware.HIDLib.Interop.SetupAPI

''' <summary>
''' Manages device enumeration and searching.
''' </summary>
Public Class HIDManagement
	Implements IDisposable
	
	#Region "Shared Methods"
	
	#Region "GetDevices Overloads"
	
	''' <summary>
	''' Returns all HID-class devices that are currently attached.
	''' </summary>
	''' <returns></returns>
	Public Overloads Shared Function GetDevices() As List(Of HIDDevice)
		Return GetDevices(0, 0, True)
	End Function
	
	''' <summary>
	''' Returns all HID-class devices.
	''' </summary>
	''' <param name="PresentOnly">If TRUE, returns only the devices currently attached. If FALSE, returns all installed devices.</param>
	''' <returns></returns>
	Public Overloads Shared Function GetDevices(ByVal PresentOnly As Boolean) As IList(Of HIDDevice)
		Return GetDevices(0, 0, PresentOnly)
	End Function
	
	''' <summary>
	''' Returns all HID-class devices for the specified VendorID that are currently attached.
	''' </summary>
	''' <param name="VendorID">The VendorID to return matching devices for.</param>
	''' <returns></returns>
	Public Overloads Shared Function GetDevices(ByVal VendorID As UShort) As IList(Of HIDDevice)
		Return GetDevices(VendorID, 0, True)
	End Function
	
	''' <summary>
	''' Returns all HID-class devices for the specified VendorID.
	''' </summary>
	''' <param name="VendorID">The VendorID to return matching devices for.</param>
	''' <param name="PresentOnly">If TRUE, returns only the devices currently attached. If FALSE, returns all installed devices.</param>
	''' <returns></returns>
	Public Overloads Shared Function GetDevices(ByVal VendorID As UShort, ByVal PresentOnly As Boolean) As IList(Of HIDDevice)
		Return GetDevices(VendorID, 0, PresentOnly)
	End Function
	
	''' <summary>
	''' Returns all HID-class devices for the specified VendorID and ProductID that are currently attached.
	''' </summary>
	''' <param name="VendorID">The VendorID to return matching devices for.</param>
	''' <param name="ProductID">The ProductID of the device(s) to return.</param>
	''' <returns></returns>
	Public Overloads Shared Function GetDevices(ByVal VendorID As UShort, ByVal ProductID As UShort) As IList(Of HIDDevice)
		Return GetDevices(VendorID, ProductID, True)
	End Function
	
	''' <summary>
	''' Returns all HID-class devices for the specified VendorID and ProductID.
	''' </summary>
	''' <param name="VendorID">The VendorID to return matching devices for.</param>
	''' <param name="ProductID">The ProductID of the device(s) to return.</param>
	''' <param name="PresentOnly">If TRUE, returns only the devices currently attached. If FALSE, returns all installed devices.</param>
	''' <returns></returns>
	''' <remarks>Specifying a VendorID or ProductID of 0 will return all matches for that parameter. Using 0 for both parameters will return all devices on the system.</remarks>
	Public Overloads Shared Function GetDevices(ByVal VendorID As UShort, ByVal ProductID As UShort, ByVal PresentOnly As Boolean) As IList(Of HIDDevice)
		Dim Devices As New List(Of HIDDevice)
		
		Dim hDevInfo As IntPtr
		
		Try
			Dim ClassGuid As New Guid
			Dim index As UInteger = 0
			
			' get the GUID of the HID class
			HidD_GetHidGuid(ClassGuid)
			
			' get a handle to all devices that are part of the HID class
			' Fun fact:  DIGCF_PRESENT worked on my machine just fine.  I reinstalled Vista, and now it no longer finds the Wiimote with that parameter enabled...
			hDevInfo = SetupDiGetClassDevs(ClassGuid, Nothing, IntPtr.Zero, DiGetClassFlagsEnum.DEVICEINTERFACE Or IIF(PresentOnly, DiGetClassFlagsEnum.PRESENT, 0))
			
			' create a new interface data struct and initialize its size
			Dim diData As SP_DEVICE_INTERFACE_DATA
			diData.cbSize = Marshal.SizeOf(diData)

			' get a device interface to a single device (enumerate all devices)
			Do While SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ClassGuid, index, diData)
				Dim size As UInteger = 0
				
				' get the buffer size for this device detail instance (returned in the size parameter)
				SetupDiGetDeviceInterfaceDetail(hDevInfo, diData, IntPtr.Zero, 0, size, IntPtr.Zero)
				
				' create a detail struct and set its size
				Dim diDetail As New SP_DEVICE_INTERFACE_DETAIL_DATA

				' yeah, yeah...well, see, on Win x86, cbSize must be 5 for some reason.  On x64, apparently 8 is what it wants.
                ' someday I should figure this out.  Thanks to Paul Miller on this
                'If IntPtr.Size = 8 Then...
				'	diDetail.cbSize = 8
				'Else
				'	diDetail.cbSize = 5
				'End If
				
				'diDetail.cbSize = Marshal.SizeOf(diDetail)
				
				diDetail.cbSize = 4 + Marshal.SystemDefaultCharSize
				
				' actually get the detail struct
				
				If SetupDiGetDeviceInterfaceDetail(hDevInfo, diData, diDetail, Marshal.SizeOf(diDetail), 0, IntPtr.Zero) Then
					Debug.WriteLine(index & " " & diDetail.DevicePath & " " & Marshal.GetLastWin32Error())
					
					' open a read/write handle to our device using the DevicePath returned
					Dim Handle As New Microsoft.Win32.SafeHandles.SafeFileHandle(CreateFile(diDetail.DevicePath, FileAccess.ReadWrite, FileShare.ReadWrite, Nothing, FileMode.Open, EFileAttributes.Overlapped, IntPtr.Zero), True)
					
					If Not Handle.IsInvalid Then
						
						' create an attributes struct and initialize the size
						Dim attrib As HIDD_ATTRIBUTES
						attrib.Size = Marshal.SizeOf(attrib)
						
						' get the attributes of the current device
						If HidD_GetAttributes(Handle.DangerousGetHandle, attrib) Then
							' if the vendor and product IDs match up
							If (attrib.VendorID = VendorID Or VendorID = 0) AndAlso (attrib.ProductID = ProductID Or ProductID = 0) Then
								Debug.WriteLine("Found it!")
								
								'TODO: Re-open device after getting report size, to set buffer properly?
								Devices.Add(New HIDDevice(New FileStream(Handle, FileAccess.ReadWrite, 8, True), diDetail.DevicePath, attrib))
							Else
								
								'Otherwise, not the device we want, so close and try the next one.
								Handle.Dispose
							End If
						Else
							'Couldn't get attribs, so close and try the next one.
							Handle.Dispose
						End If
						
					Else
						'Couldn't open the device, so screw it - move on to the next one!
					End If
				Else
					' failed to get the detail struct
					Throw New ApplicationException(String.Format("SetupDiGetDeviceInterfaceDetail failed on index {0}: {1}", index, New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error).ToString))
				End If

				' move to the next device
				index += 1
			Loop
			
			Return Devices.AsReadOnly
		Catch ex As ApplicationException
			MsgBox(ex.ToString)
			Throw
		Catch ex As Exception
			MsgBox(ex.ToString)
			Throw New ApplicationException(String.Format("Could not get device {0}:{1}: {2}", PadHex(Hex(VendorID), 4), PadHex(Hex(ProductID), 4), ex.ToString))
		Finally
			'Cleanup
			Debug.WriteLine("SetupDiDestroyDeviceInfoList")
			SetupDiDestroyDeviceInfoList(hDevInfo)
		End Try
	End Function
	
	#End Region
	
	#End Region
	
	#Region "Cleanup / Disposal"
	
	Protected Overloads Overrides Sub Finalize()
		FreeUnmanagedResources		
		
		MyBase.Finalize()
	End Sub
	
	Public Sub Dispose() Implements IDisposable.Dispose
		FreeUnmanagedResources		
		
		GC.SuppressFinalize(Me)
	End Sub
	
	Private Sub FreeUnmanagedResources
		'Close handles, etc.
	End Sub
	
	#End Region
	
End Class
